#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "map.h"

void map_init (Map* map, int width, int height, int layers)
{
	assert(map != NULL);
	
	map->width = width;
	map->height = height;
	map->layers = layers;
	
	int memsize = layers * width * height * sizeof(Tile);
	map->tile = malloc (memsize);
	memset (map->tile, 0, memsize);
	
	carray_init (&map->object, sizeof(MapObject), 64);
	carray_init (&map->actor, sizeof(Actor), 64);
	
	memset (map->spawn, 0, sizeof(map->spawn));
	map->n_spawns = 0;
}

void map_cleanup (Map* map)
{
	assert(map != NULL);
	
	free (map->tile);
	carray_cleanup (&map->object);
	carray_cleanup (&map->actor);
	
	memset (map, 0, sizeof(Map));
}

int map_add_spawn (Map* map, Coord coord)
{
	assert(map != NULL);
	assert((map->n_spawns >= 0) && (map->n_spawns <= MAX_SPAWNPOINTS));
	assert((coord.x >= 0) && (coord.x < map->width));
	assert((coord.y >= 0) && (coord.y < map->height));
	assert((coord.z >= 0) && (coord.z < map->layers));
	
	if (map->n_spawns < MAX_SPAWNPOINTS) {
		map->spawn[map->n_spawns] = coord;
		return map->n_spawns++;
	} else {
		return -1;
	}
}

MapObject* map_add_object (Map* map, Coord coord)
{
	assert(map != NULL);
	assert((coord.x >= 0) && (coord.x < map->width));
	assert((coord.y >= 0) && (coord.y < map->height));
	assert((coord.z >= 0) && (coord.z < map->layers));
	
	MapObject* object = CARRAY_APPEND (&map->object, NULL, MapObject);
	object->coord = coord;
	object->index = map->object.count;
	
	Tile* tile = &TILEC(map,coord);
	assert(!tile->has_object);
	assert(!tile->has_actor);
	
	tile->has_object = true;
	tile->object = object;
	return tile->object;
}

MapObject* map_get_object (Map* map, int index)
{
	assert(map != NULL);
	assert((index >= 0) && (index < map->object.count));
	
	return CARRAY_ELEM (&map->object, index, MapObject);
}

static void place_actor_on_tile (Map* map, Coord coord, int actor)
{
	Tile* tile = &TILEC(map,coord);
	assert(!tile->has_object);
	assert(!tile->has_actor);
	
	tile->has_actor = true;
	tile->actor = actor;
}

Actor* map_spawn_character (Map* map, int spawn, int hp, CharacterType type)
{
	assert(map != NULL);
	assert((spawn >= 0) && (spawn < map->n_spawns));
	assert(hp > 0);
	assert(type > CHARACTER_UNDEFINED);
	
	// spawn is also the new actor's id
	Coord coord = map->spawn[spawn];
	Actor* actor = CARRAY_APPEND (&map->actor, NULL, Actor);
	actor_alloc_character (actor, spawn, coord, hp, type);
	place_actor_on_tile (map, coord, spawn);
	
	return actor;
}

Actor* map_spawn_monster (Map* map, Coord coord, int hp,
                          MonsterType species, int weapon, int armor)
{
	assert(map != NULL);
	assert((coord.x >= 0) && (coord.x < map->width));
	assert((coord.y >= 0) && (coord.y < map->height));
	assert((coord.z >= 0) && (coord.z < map->layers));
	assert(hp > 0);
	assert(species > MONSTER_UNDEFINED);
	
	int id = map->actor.count;
	Actor* actor = CARRAY_APPEND (&map->actor, NULL, Actor);
	actor_alloc_monster (actor, id, coord, hp, species, weapon, armor);
	place_actor_on_tile (map, coord, id);
	
	return actor;
}

Actor* map_get_actor (Map* map, int id)
{
	assert(map != NULL);
	assert((id >= 0) && (id < map->actor.count));
	
	return CARRAY_ELEM (&map->actor, id, Actor);
}

#ifdef TEST_MAP
#include <stdio.h>
#include "dig.h"

void print_map (Map* map)
{
	printf ("Map with %dx%dx%d tiles **********************\n", map->width, map->height, map->layers);
	for (int z = 0; z < map->layers; z++) {
		printf ("-- Layer %d --\n", z);
		for (int y = 0; y < map->height; y++) {				
			for (int x = 0; x < map->width; x++) {
				Tile* t = TILE (map, x, y, z);
				char c = ' ';
				switch (t->type) {
					case TILE_STANDARD:
						if (t->is_blocking) c = '#';
						else if (t->room >= 0) {
							Coord mid = rect_middle (&map->rooms[t->room]);
							if (COORDS_EQUAL (mid, ((Coord) {x,y,z})))
								c = '0' + t->room;
						}
						break;
						
					case TILE_STAIRS:
						if (t->stairs.dir == DIR_LEVELUP)   c = '<'; else 
						if (t->stairs.dir == DIR_LEVELDOWN) c = '>'; else
						                                    c = '/';
						break;
					
					case TILE_TELEPORTER:
						c = '@';
						break;
					
					case TILE_DOOR:
						if (t->door.orientation == DOOR_HORIZONTAL) c = '=';
						else                                        c = '|';
						break;
					
					case TILE_TRIGGER:
						c = ',';
						break;
				}
				printf ("%c ", c);
			}
			printf ("\n");
		}
		printf ("\n");
	}
}

int main ()
{
	Layout layout;
	layout_init (&layout, 70, 70, 1, 1000, 1000);
	dig_init (&layout);
	
	int prev [4];
	
	prev[0] = dig_room (8, 15, prev, 0);
	prev[2] = dig_room (9, 7, prev, 1);
	prev[1] = dig_room (10, 5, prev, 1 );
	prev[0] = prev[2];
	prev[3] = dig_room (20, 14, prev, 2 );
	prev[0] = prev[3];
	prev[0] = dig_room (7, 13, prev, 1 );
	prev[1] = dig_room (9, 15, prev, 1 );
	dig_room (10, 10, prev, 4 );
	dig_cleanup ();
	
	Map map;
	map_init (&map, &layout);
	print_map (&map);
	
	map_cleanup (&map);
	layout_cleanup (&layout);
	return 0;
}

#endif /* TEST_MAP */
